篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发-线程池篇-附场景分析相关的知识,希望对你有一定的参考价值。
作者:汤圆
个人博客:javalover.cc
前面我们在创建线程时,都是直接new Thread();
这样短期来看是没有问题的,但是一旦业务量增长,线程数过多,就有可能导致内存异常OOM,CPU爆满等问题
幸运的是,Java里面有线程池的概念,而线程池的核心框架,就是我们今天的主题,Executor
接下来,就让我们一起畅游在Java线程池的海洋中吧
本节会用银行办业务的场景来对比介绍线程池的核心概念,这样理解起来会很轻松
Executor是线程池的核心框架;
和它相对应的有一个辅助工厂类Executors,这个类提供了许多工厂方法,用来创建各种各样的线程池,下面我们先看下几种常见的线程池
// 容量固定的线程池
Executor fixedThreadPool = Executors.newFixedThreadPool(5);
// 容量动态增减的线程池
Executor cachedThreadPool = Executors.newCachedThreadPool();
// 单个线程的线程池
Executor singleThreadExecutor = Executors.newSingleThreadExecutor();
// 基于调度机制的线程池(不同于上面的线程池,这个池创建的任务不会立马执行,而是定期或者延时执行)
Executor scheduledThreadPool = Executors.newScheduledThreadPool(5);
上面这些线程池的区别主要就是线程数量的不同以及任务执行的时机
下面让我们开始吧
文章如果有问题,欢迎大家批评指正,在此谢过啦
ThreadPoolExecutor
ExecutorService
ThreadPoolExecutor
在文章开头创建的几个线程池,内部都是有调用ThreadPoolExecutor
这个类的,如下所示
public static ExecutorService newFixedThreadPool(int nThreads)
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
这个类是Exexutor的一个实现类&#xff0c;关系图如下所示&#xff1a;
其中Executors就是上面介绍的辅助工厂类&#xff0c;用来创建各种线程池
接口ExecutorService是Executor的一个子接口&#xff0c;它对Executor进行了扩展&#xff0c;原有的Executor只能执行任务&#xff0c;而ExecutorService还可以管理线程池的生命周期&#xff08;下面会介绍&#xff09;
所以我们先来介绍下这个底层类&#xff0c;它的完整构造参数如下所示&#xff1a;
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
在介绍这些参数之前&#xff0c;我们可以先举个生活中的例子-去银行办业务&#xff1b;然后对比着来理解&#xff0c;会比较清晰
&#xff08;图中绿色的窗口表示一直开着&#xff09;
基本的工作流程如下所示&#xff1a;
上面的参数我们着重介绍下工作队列和拒绝策略&#xff0c;线程工厂下面再介绍
工作队列&#xff1a;
拒绝策略&#xff1a;
原话如下&#xff1a;
我们可以写几个代码来测试一下
先测试FixedThreadPool&#xff0c;代码如下&#xff1a;
public class FixedThreadPoolDemo
public static void main(String[] args)
// 创建一个固定容量为10的线程池&#xff0c;核心线程数和最大线程数都为10
ExecutorService executorService &#61; Executors.newFixedThreadPool(10);
for (int i &#61; 0; i < 1_000_000; i&#43;&#43;)
try
executorService.execute(()->
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
catch (Exception e)
e.printStackTrace();
这里我们需对VM参数做一点修改&#xff0c;让问题比较容易复现
如下所示&#xff0c;我们添加-Xmx8m -Xms8m
到VM option中(-Xmx8m&#xff1a;JVM堆的最大内存为8M&#xff0c; -Xms8m&#xff0c;JVM堆的初始化内存为8M)&#xff1a;
此时点击运行&#xff0c;就会发现报错如下&#xff1a;
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.jalon.concurrent.chapter6.FixedThreadPoolDemo.main(FixedThreadPoolDemo.java:21)
我们来分析下原因
CachedThreadPool也是类似的原因&#xff0c;只不过它是因为最大线程数为Integer.MAX_VALUE&#xff1b;
所以当任务插入的速度&#xff0c;超过了任务执行的速度&#xff0c;那么线程的数量会越来越多&#xff0c;最终导致OOM
那我们要怎么创建线程池呢&#xff1f;
可以用ThreadPoolExecutor来自定义创建&#xff0c;通过为最大线程数和工作队列都设置一个边界&#xff0c;来限制相关的数量&#xff0c;如下所示&#xff1a;
public class ThreadPoolExecutorDemo
public static void main(String[] args)
ExecutorService service &#61; new ThreadPoolExecutor(
1, // 核心线程数
1, // 最大线程数
60L, // 空闲时间
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1), // 数组工作队列&#xff0c;长度1
new ThreadPoolExecutor.DiscardPolicy()); // 拒绝策略&#xff1a;丢弃
for (int i &#61; 0; i < 1_000_000; i&#43;&#43;)
// 通过这里的打印信息&#xff0c;我们可以知道循环了3次
// 原因就是第一次的任务在核心线程中执行&#xff0c;第二次的任务放到了工作队列&#xff0c;第三次的任务被拒绝执行
System.out.println(i);
service.execute(()->
// 这里会报异常&#xff0c;是因为执行了拒绝策略&#xff08;达到了最大线程数&#xff0c;队列也满了&#xff0c;此时新进来的任务就会执行拒绝策略&#xff09;
// 这里需要注意的是&#xff0c;抛出异常后&#xff0c;代码并不会退出&#xff0c;而是卡在异常这里&#xff0c;包括主线程也会被卡住(这个是默认的拒绝策略&#xff09;
// 我们可以用其他的拒绝策略&#xff0c;比如DiscardPolicy,此时代码就会继续往下执行
System.out.println(Thread.currentThread().getName());
);
try
Thread.sleep(1000);
System.out.println("主线程 sleep ");
catch (InterruptedException e)
e.printStackTrace();
ExecutorService
Executor接口默认只有一个方法void execute(Runnable command);
&#xff0c;用来执行任务
任务一旦开启&#xff0c;我们就无法再去插手了&#xff0c;比如停止、监控等
此时就需要ExecutorService登场了&#xff0c;它是Executor的一个子接口&#xff0c;对其进行了扩展&#xff0c;方法如下&#xff1a;
public interface ExecutorService extends Executor
void shutdown(); // 优雅地关闭&#xff0c;这个关闭会持续一段时间&#xff0c;以等待已经提交的任务去执行完成&#xff08;但是在shutdown之后提交的任务会被拒绝&#xff09;
List<Runnable> shutdownNow(); // 粗暴地关闭&#xff0c;这个关闭会立即关闭所有正在执行的任务&#xff0c;并返回工作队列中等待的任务
boolean isShutdown();
boolean isTerminated();
// 用来等待线程的执行
// 如果在timeout之内&#xff0c;线程都执行完了&#xff0c;则返回true&#xff1b;
// 如果等了timeout&#xff0c;还没执行完&#xff0c;则返回false&#xff1b;
// 如果timeout之内&#xff0c;线程被中断&#xff0c;则抛出中断异常
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
从上面可以看到&#xff0c;线程池的生命周期分三步&#xff1a;
ThreadPoolExecutor
&#xff1a;核心概念就是核心线程数、最大线程数、工作队列、拒绝策略ThreadPoolExecutor
&#xff0c;为最大线程数和工作队列设置边界ExecutorService
&#xff1a;运行状态&#xff08;创建后进入&#xff09;、关闭状态&#xff08;shutdown后进入&#xff09;、已终止状态&#xff08;所有线程都执行完成后进入&#xff09;参考内容&#xff1a;
愿你的意中人亦是中意你之人